package io.javalin.core.util

import java.lang.reflect.Field
import java.lang.reflect.Method

internal val Any.kotlinFieldName // this is most likely a very stupid solution
    get() = this.javaClass.toString().removePrefix(this.parentClass.toString() + "$").takeWhile { it != '$' }

internal val Any.javaFieldName: String?
    get() = try {
        parentClass.declaredFields.find { it.isAccessible = true; it.get(it) == this }?.name
    } catch (ignored: Exception) { // Nothing really matters.
        null
    }

internal val Any.methodName: String? // broken in jdk9+ since ConstantPool has been removed
    get() {
//        val constantPool = Class::class.java.getDeclaredMethod("getConstantPool").apply { isAccessible = true }.invoke(javaClass) as ConstantPool
//        for (i in constantPool.size downTo 0) {
//            try {
//                val name = constantPool.getMemberRefInfoAt(i)[1]
//                // Autogenerated ($), constructor, or kotlin's check (fix maybe?)
//                if (name.contains("(\\$|<init>|checkParameterIsNotNull)".toRegex())) {
//                    continue
//                } else {
//                    return name
//                }
//            } catch (ignored: Exception) {
//            }
//        }
        return null
    }
internal val Any.parentClass: Class<*> get() = Class.forName(this.javaClass.name.takeWhile { it != '$' })

internal val Any.implementingClassName: String? get() = this.javaClass.name

internal val Any.isClass: Boolean get() = this is Class<*>

internal val Any.isKotlinAnonymousLambda: Boolean get() = this.javaClass.enclosingMethod != null

internal val Any.isKotlinMethodReference: Boolean get() = this.javaClass.declaredFields.any { it.name == "function" }

internal val Any.isKotlinField: Boolean get() = this.javaClass.fields.any { it.name == "INSTANCE" }

internal val Any.isJavaAnonymousLambda: Boolean get() = this.javaClass.isSynthetic

internal val Any.hasMethodName: Boolean get() = methodName != null

internal val Any.isJavaNonStaticMethodReference: Boolean get() = javaClass.declaredMethods.any { it.name == methodReferenceReflectionMethodName }

internal val Any.isJavaField: Boolean get() = this.javaFieldName != null

internal fun Any.runMethod(name: String): Any = this.javaClass.getMethod(name).apply { isAccessible = true }.invoke(this)

internal val Any.lambdaField: Field?
    get() = when {
        isKotlinField -> parentClass.getDeclaredFieldByName(kotlinFieldName)
        isJavaField -> parentClass.getDeclaredFieldByName(javaFieldName!!)
        else -> null
    }

internal fun Any.getFieldValue(fieldName: String): Any {
    val field = this::class.java.getDeclaredField(fieldName)
    field.isAccessible = true
    return field.get(this)
}

internal fun Class<*>.getMethodByName(methodName: String): Method? {
    val isName = { method: Method -> method.name == methodName }
    return declaredMethods.find(isName) ?: methods.find(isName)
}

internal fun Class<*>.getDeclaredFieldByName(methodName: String): Field? = declaredFields
        .find { it.name == methodName }

internal val Class<*>.methodsNotDeclaredByObject
    get(): Array<Method> = (declaredMethods + methods)
            .toSet()
            .filter { it.declaringClass != Object::class.java }
            .toTypedArray()

internal const val methodReferenceReflectionMethodName = "get\$Lambda"
